home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Audio, Video & Photo / Songbird 0.7.0 / Songbird_0.7.0_windows-i686-msvc8.exe / components / sbFeathersManager.js < prev    next >
Text File  |  2008-08-13  |  40KB  |  1,384 lines

  1. /**
  2. //
  3. // BEGIN SONGBIRD GPL
  4. // 
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 POTI, Inc.
  8. // http://songbirdnest.com
  9. // 
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. // 
  13. // Software distributed under the License is distributed 
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either 
  15. // express or implied. See the GPL for the specific language 
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this 
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc., 
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. // 
  23. // END SONGBIRD GPL
  24. //
  25.  */
  26.  
  27. /**
  28.  * \file sbFeathersManager.js
  29.  * \brief Coordinates the loading of feathers (combination of skin and XUL window layout)
  30.  */ 
  31.  
  32.  
  33. //
  34. // TODO:
  35. //  * Explore skin/layout versioning issues?
  36. // 
  37.  
  38. const Ci = Components.interfaces;
  39. const Cc = Components.classes; 
  40.  
  41. const CONTRACTID = "@songbirdnest.com/songbird/feathersmanager;1";
  42. const CLASSNAME = "Songbird Feathers Manager Service Interface";
  43. const CID = Components.ID("{99f24350-a67f-11db-befa-0800200c9a66}");
  44. const IID = Ci.sbIFeathersManager;
  45.  
  46.  
  47. const RDFURI_ADDON_ROOT               = "urn:songbird:addon:root" 
  48. const PREFIX_NS_SONGBIRD              = "http://www.songbirdnest.com/2007/addon-metadata-rdf#";
  49.  
  50. // Fallback layouts/skin, used by previousSkinName and previousLayoutURL
  51. // Changes to the shipped feathers must be reflected here
  52. // and in test_feathersManager.js
  53. const DEFAULT_MAIN_LAYOUT_URL         = "chrome://gonzo/content/xul/mainplayer.xul";
  54. const DEFAULT_SECONDARY_LAYOUT_URL    = "chrome://gonzo/content/xul/miniplayer.xul";
  55. const DEFAULT_SKIN_NAME               = "gonzo";
  56.  
  57. const WINDOWTYPE_SONGBIRD_PLAYER      = "Songbird:Main";
  58. const WINDOWTYPE_SONGBIRD_CORE        = "Songbird:Core";
  59.  
  60. Components.utils.import("resource://app/jsmodules/RDFHelper.jsm");
  61.  
  62. /**
  63.  * /class ArrayEnumerator
  64.  * /brief Converts a js array into an nsISimpleEnumerator
  65.  */
  66. function ArrayEnumerator(array)
  67. {
  68.   this.data = array;
  69. }
  70. ArrayEnumerator.prototype = {
  71.  
  72.   index: 0,
  73.  
  74.   getNext: function() {
  75.     return this.data[this.index++];
  76.   },
  77.  
  78.   hasMoreElements: function() {
  79.     if (this.index < this.data.length)
  80.       return true;
  81.     else
  82.       return false;
  83.   },
  84.  
  85.   QueryInterface: function(iid)
  86.   {
  87.     if (!iid.equals(Ci.nsISimpleEnumerator) &&
  88.         !iid.equals(Ci.nsISupports))
  89.       throw Components.results.NS_ERROR_NO_INTERFACE;
  90.     return this;
  91.   }
  92. }
  93.  
  94. /**
  95.  * sbISkinDescription
  96.  */
  97. function SkinDescription() {};
  98. SkinDescription.prototype = {
  99.   // TODO Expand?
  100.   requiredProperties: [ "name", "internalName" ],
  101.   optionalProperties: [ ],
  102.   QueryInterface: function(iid) {
  103.     if (!iid.equals(Ci.sbISkinDescription))
  104.       throw Components.results.NS_ERROR_NO_INTERFACE;
  105.     return this;
  106.   }
  107. };
  108.  
  109. /**
  110.  * sbILayoutDescription
  111.  */
  112. function LayoutDescription() {};
  113. LayoutDescription.prototype = {
  114.   // TODO Expand?
  115.   requiredProperties: [ "name", "url" ],
  116.   optionalProperties: [ ],
  117.   QueryInterface: function(iid) {
  118.     if (!iid.equals(Ci.sbILayoutDescription))
  119.       throw Components.results.NS_ERROR_NO_INTERFACE;
  120.     return this;
  121.   }
  122. };
  123.  
  124. /**
  125.  * Static function that verifies the contents of the given description
  126.  *
  127.  * Example:
  128.  *   try {
  129.  *     LayoutDescription.verify(layout);
  130.  *   } catch (e) {
  131.  *     reportError(e);
  132.  *   }
  133.  *
  134.  */
  135. LayoutDescription.verify = SkinDescription.verify = function( description ) 
  136. {
  137.   for (var i = 0; i < this.prototype.requiredProperties.length; i++) {
  138.     var property = this.prototype.requiredProperties[i];
  139.     if (! (typeof(description[property]) == 'string'
  140.              && description[property].length > 0)) 
  141.     {
  142.       throw("Invalid description. '" + property + "' is a required property.");
  143.     }
  144.   }
  145. }
  146.  
  147.  
  148. /**
  149.  * /class AddonMetadataReader
  150.  * Responsible for reading addon metadata and performing 
  151.  * registration with FeathersManager
  152.  */
  153. function AddonMetadataReader() {};
  154.  
  155. AddonMetadataReader.prototype = {
  156.   _manager: null,
  157.  
  158.   /**
  159.    * Populate FeathersManager using addon metadata
  160.    */
  161.   loadMetadata: function(manager) {
  162.     //debug("AddonMetadataReader: loadMetadata\n");
  163.     this._manager = manager;
  164.     
  165.     var addons = RDFHelper.help(
  166.       "rdf:addon-metadata",
  167.       "urn:songbird:addon:root",
  168.       RDFHelper.DEFAULT_RDF_NAMESPACES
  169.     );
  170.     
  171.     for (var i = 0; i < addons.length; i++) {
  172.       // first a little workaround to for backwards compatibility 
  173.       // with the now obsolete <feathers> element
  174.       // TODO: remove this when we stop supporting 0.4 feathers
  175.       var feathersHub = addons[i];
  176.       if (feathersHub.feathers) {
  177.         Components.utils.reportError("Feathers Metadata Reader: The <feathers/> element in " +
  178.                        "install.rdf is deprecated and will go away in a future version.");
  179.         feathersHub = feathersHub.feathers[0];
  180.       }
  181.       
  182.       if (feathersHub.skin) {
  183.         var skins = feathersHub.skin;
  184.         for (var j = 0; j < skins.length; j++) {
  185.           try {
  186.             this._registerSkin(addons[i], skins[j]);
  187.           }
  188.           catch (e) {
  189.             this._reportErrors("", [  "An error occurred while processing " +
  190.                   "extension " + addons[i].Value + ".  Exception: " + e  ]);
  191.           }
  192.         }
  193.       }
  194.       
  195.       if (feathersHub.layout) {
  196.         var layouts = feathersHub.layout;
  197.         for (var j = 0; j < layouts.length; j++) {
  198.           try {
  199.             this._registerLayout(addons[i], layouts[j]);
  200.           }
  201.           catch (e) {
  202.             this._reportErrors("", [  "An error occurred while processing " +
  203.                   "extension " + addons[i].Value + ".  Exception: " + e  ]);
  204.           }
  205.         }
  206.       }
  207.     }
  208.   },
  209.   
  210.   
  211.   /**
  212.    * Extract skin metadata
  213.    */
  214.   _registerSkin: function _registerSkin(addon, skin) {
  215.     var description = new SkinDescription();
  216.     
  217.     // Array of error messages
  218.     var errorList = [];
  219.     
  220.     // NB: there is a "verify" function as well, 
  221.     //     but here we build and verify at the same time
  222.     for each (var prop in SkinDescription.prototype.requiredProperties) {
  223.       if(skin[prop][0]) {
  224.         description[prop] = skin[prop][0];
  225.       }
  226.       else {
  227.         errorList.push("Missing required <"+prop+"> element.");
  228.       }
  229.     }
  230.  
  231.     for each (var prop in SkinDescription.prototype.optionalProperties) {
  232.       if(skin[prop][0]) {
  233.         description[prop] = skin[prop][0];
  234.       }
  235.     }
  236.     
  237.     // If errors were encountered, then do not submit 
  238.     // to the Feathers Manager
  239.     if (errorList.length > 0) {
  240.       this._reportErrors(
  241.           "Ignoring skin addon in the install.rdf of extension " +
  242.           addon.Value + ". Message: ", errorList);
  243.       return;
  244.     }
  245.     
  246.     // Submit description
  247.     this._manager.registerSkin(description);
  248.     //debug("AddonMetadataReader: registered skin " + description.internalName
  249.     //        + " from addon " + addon.Value + " \n");
  250.  
  251.     if (skin.compatibleLayout) {
  252.       var compatibleLayouts = skin.compatibleLayout;
  253.       var hasRegisteredDefault = false;
  254.       for (var i = 0; i < compatibleLayouts.length; i++) {
  255.         var compatibleLayout = compatibleLayouts[i];
  256.  
  257.         var layoutUrl;
  258.         // TODO: FIXME! this is inconsistent with other capitalizations!!
  259.         if(compatibleLayout.layoutURL && compatibleLayout.layoutURL[0].length != 0) {
  260.           layoutUrl  = compatibleLayout.layoutURL[0];
  261.         }
  262.         else {
  263.           errorList.push("layoutUrl was missing or incorrect.");
  264.           continue;
  265.         }
  266.   
  267.         var showChrome = false;
  268.         if (compatibleLayout.showChrome && 
  269.             compatibleLayout.showChrome[0] == "true") {
  270.           showChrome = true;
  271.         }
  272.         var onTop = false
  273.         if (compatibleLayout.onTop && 
  274.             compatibleLayout.onTop[0] == "true") {
  275.           onTop = true;
  276.         }
  277.   
  278.         this._manager.assertCompatibility(
  279.           layoutUrl, 
  280.           description.internalName, 
  281.           showChrome, 
  282.           onTop
  283.         );
  284.  
  285.         // If this is the first element in the RDF - or the layout is set to
  286.         // be the default layout, let's assign these attributes here.
  287.         if ((i == 0) || 
  288.             (compatibleLayout.isDefault && 
  289.             compatibleLayout.isDefault[0] == "true")) 
  290.         {
  291.           this._manager.setDefaultLayout(layoutUrl, description.internalName);
  292.           
  293.           if (hasRegisteredDefault) {
  294.             Components.utils.reportError(
  295.               "A default layout has already been assigned for " + 
  296.               description.internalName
  297.             );
  298.           }
  299.           
  300.           hasRegisteredDefault = true;
  301.         }
  302.       }
  303.       
  304.       if (errorList.length > 0) {
  305.         this._reportErrors(
  306.             "Ignoring a <compatibleLayout> in the install.rdf of extension " +
  307.             addon.Value + ". Message: ", errorList);
  308.         return;
  309.       }
  310.     }
  311.   },
  312.  
  313.   /**
  314.    * Extract layout metadata
  315.    */
  316.   _registerLayout: function _processLayout(addon, layout) {
  317.     var description = new LayoutDescription();
  318.  
  319.     // Array of error messages
  320.     var errorList = [];
  321.     
  322.     // NB: there is a "verify" function as well, 
  323.     //     but here we build and verify at the same time
  324.     for each (var prop in LayoutDescription.prototype.requiredProperties) {
  325.       if(layout[prop][0]) {
  326.         description[prop] = layout[prop][0];
  327.       }
  328.       else {
  329.         errorList.push("Missing required <"+prop+"> element.");
  330.       }
  331.     }
  332.  
  333.     for each (var prop in LayoutDescription.prototype.optionalProperties) {
  334.       if(layout[prop][0]) {
  335.         description[prop] = layout[prop][0];
  336.       }
  337.     }
  338.  
  339.     // If errors were encountered, then do not submit 
  340.     // to the Feathers Manager
  341.     if (errorList.length > 0) {
  342.       this._reportErrors(
  343.           "Ignoring layout addon in the install.rdf of extension " +
  344.           addon.Value + ". Message: ", errorList);
  345.       return;
  346.     }
  347.  
  348.     // Submit description
  349.     this._manager.registerLayout(description);
  350.     //debug("AddonMetadataReader: registered layout " + description.name +
  351.     //     " from addon " + addon.Value + "\n");    
  352.  
  353.     // TODO: should we error out here if there are errors already?
  354.     if (layout.compatibleSkin) {
  355.       var compatibleSkins = layout.compatibleSkin;
  356.       for (var i = 0; i < compatibleSkins.length; i++) {
  357.         var compatibleSkin = compatibleSkins[i];
  358.   
  359.         var internalName;
  360.         if (compatibleSkin.internalName && 
  361.            compatibleSkin.internalName[0].length != 0) 
  362.         {
  363.           internalName  = compatibleSkin.internalName[0];
  364.         }
  365.         else {
  366.           errorList.push("internalName was missing or incorrect.");
  367.           continue;
  368.         }
  369.   
  370.         var showChrome = false;
  371.         if (compatibleSkin.showChrome && 
  372.             compatibleSkin.showChrome[0] == "true") {
  373.           showChrome = true;
  374.         }
  375.         var onTop = false;
  376.         if (compatibleSkin.onTop && 
  377.             compatibleSkin.onTop[0] == "true") {
  378.           onTop = true;
  379.         }
  380.   
  381.         this._manager.assertCompatibility(
  382.           description.url, 
  383.           internalName, 
  384.           showChrome, 
  385.           onTop
  386.         );
  387.       }
  388.     }
  389.  
  390.     // Report errors
  391.     if (errorList.length > 0) {
  392.       this._reportErrors(
  393.           "Error finding compatibility information for layout " +
  394.           description.name + " in the install.rdf " +
  395.           "of extension " + addon.Value + ". Message: ", errorList);
  396.     }
  397.   },
  398.   
  399.   /**
  400.    * \brief Dump a list of errors to the console and jsconsole
  401.    *
  402.    * \param contextMessage Additional prefix to use before every line
  403.    * \param errorList Array of error messages
  404.    */
  405.   _reportErrors: function _reportErrors(contextMessage, errorList) {
  406.     for (var i = 0; i  < errorList.length; i++) {
  407.       Components.utils.reportError("Feathers Metadata Reader: " 
  408.                                        + contextMessage + errorList[i]);
  409.     }
  410.   }
  411. }
  412.  
  413.  
  414.  
  415.  
  416.  
  417.  
  418.  
  419.  
  420.  
  421.  
  422.  
  423. /**
  424.  * /class FeathersManager
  425.  * /brief Coordinates the loading of feathers
  426.  *
  427.  * Acts as a registry for skins and layout (known as feathers)
  428.  * and manages compatibility and selection.
  429.  *
  430.  * \sa sbIFeathersManager
  431.  */
  432. function FeathersManager() {
  433.  
  434.   var os      = Cc["@mozilla.org/observer-service;1"]
  435.                       .getService(Ci.nsIObserverService);
  436.   // We need to unhook things on shutdown
  437.   os.addObserver(this, "quit-application", false);
  438.   
  439.   this._skins = {};
  440.   this._layouts = {};
  441.   this._skinDefaults = {};
  442.   this._mappings = {};
  443.   this._listeners = [];
  444. };
  445. FeathersManager.prototype = {
  446.   constructor: FeathersManager,
  447.   
  448.   _layoutDataRemote: null,
  449.   _skinDataRemote: null,
  450.  
  451.   _previousLayoutDataRemote: null,
  452.   _previousSkinDataRemote: null,
  453.   
  454.   _showChromeDataRemote: null,
  455.  
  456.   _switching: false,
  457.  
  458.   
  459.   // Hash of skin descriptions keyed by internalName (e.g. classic/1.0)
  460.   _skins: null,
  461.   
  462.   // Hash of layout descriptions keyed by URL
  463.   _layouts: null,
  464.   
  465.   // Hash of default layouts for skins.
  466.   _skinDefaults: null,
  467.                                                                   
  468.   
  469.   
  470.   // Hash of layout URL to hash of compatible skin internalNames, pointing to 
  471.   // {showChrome,onTop} objects.  
  472.   //
  473.   // eg
  474.   // {  
  475.   //     mainwin.xul: {
  476.   //       blueskin: {showChrome:true, onTop:false},
  477.   //       redskin: {showChrome:false, onTop:true},
  478.   //     }
  479.   // }
  480.   //
  481.   // Compatibility is determined by whether or not a internalName
  482.   // key is *defined* in the hash, not the actual value it points to.
  483.   _mappings: null,
  484.   
  485.   
  486.   // Array of sbIFeathersChangeListeners
  487.   _listeners: null,
  488.  
  489.   _layoutCount: 0,
  490.   _skinCount: 0,
  491.   
  492.   _initialized: false,
  493.   
  494.  
  495.   /**
  496.    * Initializes dataremotes and triggers the AddonMetadataReader
  497.    * to explore installed extensions and register any feathers.
  498.    *
  499.    * Note that this function is not run until a get method is called
  500.    * on the feathers manager.  This is to defer loading the metadata
  501.    * as long as possible and avoid impacting startup time.
  502.    * 
  503.    */
  504.   _init: function init() {
  505.   
  506.     // If already initialized do nothing
  507.     if (this._initialized) {
  508.       return;
  509.     }
  510.  
  511.     // If the safe-mode dialog was requested to disable all addons, our
  512.     // basic layouts and default skin have been disabled too. We need to 
  513.     // check if that's the case, and reenable them if needed
  514.     this._ensureAddOnEnabled("gonzo@songbirdnest.com");
  515.     
  516.     // Make dataremotes to persist feathers settings
  517.     var createDataRemote =  new Components.Constructor(
  518.                   "@songbirdnest.com/Songbird/DataRemote;1",
  519.                   Ci.sbIDataRemote, "init");
  520.  
  521.     this._layoutDataRemote = createDataRemote("feathers.selectedLayout", null);
  522.     this._skinDataRemote = createDataRemote("selectedSkin", "general.skins.");
  523.     
  524.     this._previousLayoutDataRemote = createDataRemote("feathers.previousLayout", null);
  525.     this._previousSkinDataRemote = createDataRemote("feathers.previousSkin", null);
  526.     
  527.     // TODO: Rename accessibility.enabled?
  528.     this._showChromeDataRemote = createDataRemote("accessibility.enabled", null);
  529.     
  530.     // Load the feathers metadata
  531.     var metadataReader = new AddonMetadataReader();
  532.     metadataReader.loadMetadata(this);
  533.     
  534.     // If no layout url has been specified, set to default
  535.     if (this._layoutDataRemote.stringValue == "") {
  536.       this._layoutDataRemote.stringValue = DEFAULT_MAIN_LAYOUT_URL;
  537.     }
  538.     
  539.     this._initialized = true;
  540.   },
  541.   
  542.   /**
  543.    * Called on xpcom-shutdown
  544.    */
  545.   _deinit: function deinit() {
  546.     this._skins = null;
  547.     this._layouts = null;
  548.     this._mappings = null;
  549.     this._listeners = null;
  550.     this._layoutDataRemote = null;
  551.     this._skinDataRemote = null;
  552.     this._previousLayoutDataRemote = null;
  553.     this._previousSkinDataRemote = null;
  554.     this._showChromeDataRemote = null;
  555.   },
  556.     
  557.   /**
  558.    * \sa sbIFeathersManager
  559.    */
  560.   get currentSkinName() {
  561.     this._init();
  562.     return this._skinDataRemote.stringValue;
  563.   },
  564.   
  565.   
  566.   /**
  567.    * \sa sbIFeathersManager
  568.    */  
  569.   get currentLayoutURL() {
  570.     this._init();
  571.     return this._layoutDataRemote.stringValue;
  572.   },
  573.   
  574.   
  575.   /**
  576.    * \sa sbIFeathersManager
  577.    */  
  578.   get previousSkinName() {
  579.     this._init();
  580.     
  581.     // Test to make sure the previous skin exists
  582.     var skin = this.getSkinDescription(this._previousSkinDataRemote.stringValue);
  583.     
  584.     // If the skin exists, then return the skin name
  585.     if (skin) {
  586.       return skin.internalName;
  587.     }
  588.     
  589.     // Otherwise, return the default skin
  590.     return DEFAULT_SKIN_NAME;
  591.   },
  592.   
  593.   
  594.   /**
  595.    * \sa sbIFeathersManager
  596.    */  
  597.   get previousLayoutURL() {
  598.     this._init();
  599.     
  600.     // Test to make sure the previous layout exists
  601.     var layout = this.getLayoutDescription(this._previousLayoutDataRemote.stringValue);
  602.     
  603.     // If the layout exists, then return the url/identifier
  604.     if (layout) {
  605.       return layout.url;
  606.     }
  607.     
  608.     // Otherwise, return the default 
  609.     
  610.     // Use the main default unless it is currently
  611.     // active. This way if the user reverts for the
  612.     // first time they will end up in the miniplayer.
  613.     var layoutURL = DEFAULT_MAIN_LAYOUT_URL;
  614.     if (this.currentLayoutURL == layoutURL) {
  615.       layoutURL = DEFAULT_SECONDARY_LAYOUT_URL;
  616.     }
  617.     
  618.     return layoutURL;    
  619.   },
  620.  
  621.  
  622.   /**
  623.    * \sa sbIFeathersManager
  624.    */ 
  625.   get skinCount() {
  626.     this._init();
  627.     return this._skinCount;
  628.   },
  629.   
  630.   /**
  631.    * \sa sbIFeathersManager
  632.    */  
  633.   get layoutCount() {
  634.     this._init();
  635.     return this._layoutCount;
  636.   },
  637.  
  638.  
  639.   /**
  640.    * \sa sbIFeathersManager
  641.    */
  642.   getSkinDescriptions: function getSkinDescriptions() {
  643.     this._init();      
  644.     // Copy all the descriptions into an array, and then return an enumerator
  645.     return new ArrayEnumerator( [this._skins[key] for (key in this._skins)] );
  646.   },
  647.  
  648.   /**
  649.    * \sa sbIFeathersManager
  650.    */
  651.   getLayoutDescriptions: function getLayoutDescriptions() {
  652.     this._init();        
  653.     // Copy all the descriptions into an array, and then return an enumerator
  654.     return new ArrayEnumerator( [this._layouts[key] for (key in this._layouts)] );
  655.   },
  656.   
  657.   
  658.   /**
  659.    * \sa sbIFeathersManager
  660.    */  
  661.   registerSkin: function registerSkin(skinDesc) {
  662.   
  663.     SkinDescription.verify(skinDesc);
  664.     
  665.     if (this._skins[skinDesc.internalName] == null) {
  666.       this._skinCount++;
  667.     }
  668.     this._skins[skinDesc.internalName] = skinDesc;
  669.     
  670.     // Notify observers
  671.     this._onUpdate();
  672.   },
  673.  
  674.   /**
  675.    * \sa sbIFeathersManager
  676.    */
  677.   unregisterSkin: function unregisterSkin(skinDesc) {
  678.     if (this._skins[skinDesc.internalName]) {
  679.       delete this._skins[skinDesc.internalName];
  680.       this._skinCount--;
  681.       
  682.       // Notify observers
  683.       this._onUpdate();
  684.     }
  685.   },
  686.  
  687.   /**
  688.    * \sa sbIFeathersManager
  689.    */
  690.   getSkinDescription: function getSkinDescription(internalName) {
  691.     this._init();
  692.     return this._skins[internalName];
  693.   },
  694.   
  695.   
  696.   /**
  697.    * \sa sbIFeathersManager
  698.    */  
  699.   registerLayout: function registerLayout(layoutDesc) {
  700.     LayoutDescription.verify(layoutDesc);
  701.  
  702.     if (this._layouts[layoutDesc.url] == null) {
  703.       this._layoutCount++;
  704.     }
  705.     this._layouts[layoutDesc.url] = layoutDesc;
  706.     
  707.     // Notify observers
  708.     this._onUpdate();
  709.   },
  710.  
  711.   /**
  712.    * \sa sbIFeathersManager
  713.    */
  714.   unregisterLayout: function unregisterLayout(layoutDesc) {
  715.     if (this._layouts[layoutDesc.url]) {
  716.       delete this._layouts[layoutDesc.url];
  717.       this._layoutCount--;
  718.       
  719.       // Notify observers
  720.       this._onUpdate();  
  721.     }  
  722.   },
  723.     
  724.   /**
  725.    * \sa sbIFeathersManager
  726.    */    
  727.   getLayoutDescription: function getLayoutDescription(url) {
  728.     this._init();
  729.     return this._layouts[url];
  730.   }, 
  731.  
  732.   
  733.   /**
  734.    * \sa sbIFeathersManager
  735.    */
  736.   assertCompatibility: 
  737.   function assertCompatibility(layoutURL, internalName, aShowChrome, aOnTop) {
  738.     if (! (typeof(layoutURL) == "string" && typeof(internalName) == 'string')) {
  739.       throw Components.results.NS_ERROR_INVALID_ARG;
  740.     }
  741.     if (this._mappings[layoutURL] == null) {
  742.       this._mappings[layoutURL] = {};
  743.     }
  744.     this._mappings[layoutURL][internalName] = {showChrome: aShowChrome, onTop: aOnTop};
  745.  
  746.     // check if this layout/skin combination has already been seen, 
  747.     // if it hasn't then we want to switch to it in openPlayerWindow,
  748.     // so remember it
  749.     var branch = this.getFeatherPrefBranch(layoutURL, internalName);
  750.     var seen = false;
  751.     try {
  752.       seen = branch.getBoolPref("seen");
  753.     } catch (e) { }
  754.     if (!seen) {
  755.       branch.setBoolPref("seen", true);
  756.       if (!this._autoswitch) {
  757.         this._autoswitch = {};
  758.         this._autoswitch.skin = internalName;
  759.         this._autoswitch.layoutURL = layoutURL;
  760.       }
  761.     }
  762.     
  763.     // Notify observers
  764.     this._onUpdate();
  765.   },
  766.  
  767.   /**
  768.    * \sa sbIFeathersManager
  769.    */
  770.   unassertCompatibility: function unassertCompatibility(layoutURL, internalName) {
  771.     if (this._mappings[layoutURL]) {
  772.       delete this._mappings[layoutURL][internalName];
  773.       
  774.       // Notify observers
  775.       this._onUpdate();
  776.     }  
  777.   },
  778.   
  779.   /**
  780.    * \sa sbIFeathersManager
  781.    */
  782.   setDefaultLayout: function setDefaultLayout(aLayoutURL, aInternalName) {
  783.     if (!(typeof(aLayoutURL) == "string" && 
  784.           typeof(aInternalName) == "string")) 
  785.     {
  786.       throw Components.results.NS_ERROR_INVALID_ARG;
  787.     }
  788.     
  789.     this._skinDefaults[aInternalName] = aLayoutURL;
  790.     this._onUpdate();  // notify observers
  791.   },
  792.   
  793.   /**
  794.    * \sa sbIFeathersManager
  795.    */
  796.   getDefaultLayout: function getDefaultLayout(aInternalName) {
  797.     if (!typeof(aInternalName) == "string") {
  798.       throw Components.results.NS_ERROR_INVALID_ARG;
  799.     }
  800.     
  801.     this._init();
  802.     var defaultLayoutURL = this._skinDefaults[aInternalName];
  803.     
  804.     // If a default URL isn't registered, just use the first compatible 
  805.     // layout registered for the skin identifier.
  806.     if (!defaultLayoutURL) {
  807.       for (var curLayoutURL in this._mappings) {
  808.         if (aInternalName in this._mappings[curLayoutURL]) {
  809.           defaultLayoutURL = curLayoutURL;
  810.           break;
  811.         }
  812.       }
  813.     }
  814.     
  815.     // Something is terribly wrong - no layouts are registered for this skin
  816.     if (!defaultLayoutURL) {
  817.       throw Components.results.NS_ERROR_FAILURE;
  818.     }
  819.     
  820.     return defaultLayoutURL;
  821.   },
  822.     
  823.   /**
  824.    * \sa sbIFeathersManager
  825.    */
  826.   isChromeEnabled: function isChromeEnabled(layoutURL, internalName) {
  827.     this._init();
  828.     
  829.     // TEMP fix for the Mac to enable the titlebar on the main window.
  830.     // See Bug 4363
  831.     var sysInfo = Cc["@mozilla.org/system-info;1"]
  832.                             .getService(Ci.nsIPropertyBag2);
  833.     var platform = sysInfo.getProperty("name");
  834.     
  835.     if (this._mappings[layoutURL]) {
  836.       if (this._mappings[layoutURL][internalName]) {
  837.         return this._mappings[layoutURL][internalName].showChrome == true;
  838.       }
  839.     }
  840.    
  841.     return false; 
  842.   },
  843.  
  844.  
  845.   getFeatherPrefBranch: function getFeatherPrefBranch (layoutURL, internalName) {
  846.     var prefs = Cc["@mozilla.org/preferences-service;1"]
  847.       .getService(Ci.nsIPrefService);
  848.  
  849.     // a really simple url escaping algorithm
  850.     // turn all non-alphanumeric characters into:
  851.     //   "_" upper case hex charactre code "_"
  852.     function escape_url(url) {
  853.       return url.replace(/[^a-zA-Z0-9]/g, 
  854.           function(c) { 
  855.             return '_'+(c.charCodeAt(0).toString(16)).toUpperCase()+'_'; });
  856.     }
  857.  
  858.     var branchName = 'songbird.feather.' +
  859.       (internalName?internalName:'null') + '.' +
  860.       (layoutURL?escape_url(layoutURL):'null') + '.';
  861.  
  862.     return prefs.getBranch(branchName);
  863.   },
  864.  
  865.  
  866.   canOnTop: function canOnTop(layoutURL, internalName) {
  867.     this._init();
  868.     
  869.     if (this._mappings[layoutURL]) {
  870.       if (this._mappings[layoutURL][internalName]) {
  871.         return this._mappings[layoutURL][internalName].onTop == true;
  872.       }
  873.     }
  874.    
  875.     return false; 
  876.   },
  877.  
  878.  
  879.   isOnTop: function isOnTop(layoutURL, internalName) {
  880.     this._init();
  881.  
  882.     if (!this.canOnTop(layoutURL, internalName)) {
  883.       return false;
  884.     }
  885.  
  886.     var prefBranch = this.getFeatherPrefBranch(layoutURL, null);
  887.     if (prefBranch.prefHasUserValue('on_top')) {
  888.       return prefBranch.getBoolPref('on_top');
  889.     }
  890.     
  891.     return false;
  892.   },
  893.  
  894.  
  895.   setOnTop: function setOnTop(layoutURL, internalName, onTop) {
  896.     this._init();
  897.     
  898.     if (!this.canOnTop(layoutURL, internalName)) {
  899.       return false;
  900.     }
  901.  
  902.     var prefBranch = this.getFeatherPrefBranch(layoutURL, null);
  903.     prefBranch.setBoolPref('on_top', onTop);
  904.  
  905.     return;
  906.   },
  907.  
  908.  
  909.   /* FIXME: add the ability to observe onTop state */
  910.  
  911.  
  912.   /**
  913.    * \sa sbIFeathersManager
  914.    */
  915.   getSkinsForLayout: function getSkinsForLayout(layoutURL) {
  916.     this._init();
  917.  
  918.     var skins = [];
  919.     
  920.     // Find skin descriptions that are compatible with the given layout.
  921.     if (this._mappings[layoutURL]) {
  922.       for (internalName in this._mappings[layoutURL]) {
  923.         var desc = this.getSkinDescription(internalName);
  924.         if (desc) {
  925.           skins.push(desc);
  926.         }
  927.       }
  928.     }   
  929.     return new ArrayEnumerator( skins );
  930.   },
  931.   
  932.   
  933.   /**
  934.    * \sa sbIFeathersManager
  935.    */
  936.   getLayoutsForSkin: function getLayoutsForSkin(internalName) {
  937.     return new ArrayEnumerator( this._getLayoutsArrayForSkin(internalName) );
  938.   },
  939.  
  940.  
  941.   /**
  942.    * \sa sbIFeathersManager
  943.    */
  944.   switchFeathers: function switchFeathers(layoutURL, internalName) {
  945.     // don't allow this call if we're already switching
  946.     if (this._switching) {
  947.       return;
  948.     }
  949.  
  950.     this._init();
  951.  
  952.     layoutDescription = this.getLayoutDescription(layoutURL);
  953.     skinDescription = this.getSkinDescription(internalName);
  954.     
  955.     // Make sure we know about the requested skin and layout
  956.     if (layoutDescription == null || skinDescription == null) {
  957.       throw new Components.Exception("Unknown layout/skin passed to switchFeathers");
  958.     }
  959.     
  960.     // Check compatibility.
  961.     // True/false refer to the showChrome value, so check for undefined
  962.     // to determine compatibility.
  963.     if (this._mappings[layoutURL][internalName] === undefined) {
  964.       throw new Components.Exception("Skin [" + internalName + "] and Layout [" + layoutURL +
  965.             " are not compatible");
  966.     } 
  967.     
  968.     // Notify that a select is about to occur
  969.     this._onSelect(layoutDescription, skinDescription);
  970.     
  971.     // Remember the current feathers so that we can revert later if needed
  972.     this._previousLayoutDataRemote.stringValue = this.currentLayoutURL;
  973.     this._previousSkinDataRemote.stringValue = this.currentSkinName;
  974.  
  975.     // close the player window *before* changing the skin
  976.     // otherwise Gecko tries to load an image that will go away right after and crashes
  977.     // (songbird bug 3965)
  978.     this._closePlayerWindow();
  979.     
  980.     var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  981.     var callback = new FeathersManager_switchFeathers_callback(this, layoutURL, internalName);
  982.     this._switching = true;
  983.     timer.initWithCallback(callback, 0, Ci.nsITimer.TYPE_ONE_SHOT);
  984.   },
  985.   
  986.   /**
  987.    * \sa sbIFeathersManager
  988.    */
  989.   switchToNextLayout: function switchToNextLayout() {
  990.     var curSkinName = this.currentSkinName;
  991.     var curLayoutURL = this.currentLayoutURL;
  992.     
  993.     // Find the next layout (if one exists):
  994.     var nextLayout;
  995.     var layouts = this._getLayoutsArrayForSkin(curSkinName);
  996.     for (var i = 0; i < layouts.length; i++) {
  997.       if (layouts[i].url == curLayoutURL) {
  998.         if (i >= layouts.length - 1) {
  999.           nextLayout = layouts[0];
  1000.         } 
  1001.         else {
  1002.           nextLayout = layouts[i+1];
  1003.         }
  1004.       }
  1005.     }
  1006.     
  1007.     if (nextLayout != null && nextLayout.url != curLayoutURL) {
  1008.       this.switchFeathers(nextLayout.url, curSkinName);
  1009.     }
  1010.   },
  1011.   
  1012.   
  1013.   /**
  1014.    * \sa sbIFeathersManager
  1015.    * Relaunch the main window
  1016.    */
  1017.   openPlayerWindow: function openPlayerWindow() {
  1018.     
  1019.     this._init();
  1020.  
  1021.     // First, check if we should auto switch to a new skin/layout
  1022.     // (but only if we're not already in the middle of a switch)
  1023.     if (this._autoswitch && !this._switching) {
  1024.       this._layoutDataRemote.stringValue = this._autoswitch.layoutURL;
  1025.       this._skinDataRemote.stringValue = this._autoswitch.skin;
  1026.       this._autoswitch = null;
  1027.     } 
  1028.     
  1029.     // Check to make sure the current feathers are valid
  1030.     var layoutDescription = this.getLayoutDescription(this.currentLayoutURL);
  1031.     var skinDescription = this.getSkinDescription(this.currentSkinName);
  1032.     if (layoutDescription == null || skinDescription == null) {
  1033.       // The current feathers are invalid. Switch to the defaults.
  1034.       this.switchFeathers(DEFAULT_MAIN_LAYOUT_URL, DEFAULT_SKIN_NAME);
  1035.       return;
  1036.     }
  1037.     
  1038.     
  1039.     var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]
  1040.                                    .getService(Ci.nsIWindowMediator);
  1041.  
  1042.     // The core window (plugin host) is the only window which cannot be shut down
  1043.     var coreWindow = windowMediator.getMostRecentWindow(WINDOWTYPE_SONGBIRD_CORE);  
  1044.  
  1045.     // If no core window exists, then we are probably in test mode.
  1046.     // Therefore do nothing.
  1047.     if (coreWindow == null) {
  1048.       dump("FeathersManager.openPlayerWindow: unable to find window of type Songbird:Core. Test mode?\n");
  1049.       return;
  1050.     }
  1051.  
  1052.     // Determine window features.  If chrome is enabled, make resizable.
  1053.     // Otherwise remove the titlebar.
  1054.     var chromeFeatures = "chrome,modal=no,resizable=yes,centerscreen,toolbar=yes,popup=no";
  1055.     var showChrome = this.isChromeEnabled(this.currentLayoutURL, this.currentSkinName);
  1056.     if (showChrome) {
  1057.        chromeFeatures += ",titlebar=yes";
  1058.     } else {
  1059.        chromeFeatures += ",titlebar=no";
  1060.     }
  1061.     
  1062.     // Set the global chrome (window border and title) flag
  1063.     this._setChromeEnabled(showChrome);
  1064.     
  1065.     // Open the new player window
  1066.     var newMainWin = coreWindow.open(this.currentLayoutURL, "", chromeFeatures);
  1067.     newMainWin.focus();
  1068.   },
  1069.   
  1070.   
  1071.   /**
  1072.    * \sa sbIFeathersManager
  1073.    */  
  1074.   addListener: function addListener(listener) {
  1075.     if (! (listener instanceof Ci.sbIFeathersManagerListener))
  1076.     {
  1077.       throw Components.results.NS_ERROR_INVALID_ARG;
  1078.     }
  1079.     this._listeners.push(listener);
  1080.   },
  1081.   
  1082.   /**
  1083.    * \sa sbIFeathersManager
  1084.    */  
  1085.   removeListener: function removeListener(listener) {
  1086.     var index = this._listeners.indexOf(listener);
  1087.     if (index > -1) {
  1088.       this._listeners.splice(index,1);
  1089.     }
  1090.   },
  1091.   
  1092.   
  1093.   /**
  1094.    * Get an array of the layouts for the current skin.
  1095.    */
  1096.   _getLayoutsArrayForSkin: function _getLayoutsArrayForSkin(internalName) {
  1097.     this._init();
  1098.     
  1099.     var layouts = [];
  1100.     
  1101.     // Find skin descriptions that are compatible with the given layout.
  1102.     for (var layout in this._mappings) {
  1103.       if (internalName in this._mappings[layout]) {
  1104.         var desc = this.getLayoutDescription(layout);
  1105.         if (desc) {
  1106.           layouts.push(desc);
  1107.         }      
  1108.       }
  1109.     }
  1110.     
  1111.     return layouts;
  1112.   },
  1113.  
  1114.  
  1115.   /**
  1116.    * Close all player windows (except the plugin host)
  1117.    */
  1118.   _closePlayerWindow: function _closePlayerWindow() {
  1119.     var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]
  1120.                                    .getService(Ci.nsIWindowMediator);
  1121.  
  1122.     // The core window (plugin host) is the only window which cannot be shut down
  1123.     var coreWindow = windowMediator.getMostRecentWindow(WINDOWTYPE_SONGBIRD_CORE);  
  1124.     
  1125.     // If no core window exists, then we are probably in test mode.
  1126.     // Therefore do nothing.
  1127.     if (coreWindow == null) {
  1128.       dump("FeathersManager._closePlayerWindow: unable to find window of type Songbird:Core. Test mode?\n");
  1129.       return;
  1130.     }
  1131.  
  1132.     // Close all open windows other than the core, dominspector, and venkman.
  1133.     // This is needed in order to reset window chrome settings.
  1134.     var playerWindows = windowMediator.getEnumerator(null);
  1135.     while (playerWindows.hasMoreElements()) {
  1136.       var window = playerWindows.getNext();
  1137.       
  1138.       if (window != coreWindow) {
  1139.         
  1140.         // Don't close domi or other debug windows... that's just annoying
  1141.         var isDebugWindow = false;
  1142.         try {    
  1143.           var windowElement = window.document.documentElement;
  1144.           var windowID = windowElement.getAttribute("id");
  1145.           if (windowID == "JSConsoleWindow" || 
  1146.               windowID == "winInspectorMain" || 
  1147.               windowID == "venkman-window") 
  1148.           {
  1149.             isDebugWindow = true;
  1150.           }
  1151.         } catch (e) {}
  1152.         
  1153.         if (!isDebugWindow) {
  1154.           
  1155.           // Ask nicely.  The window should be able to cancel the onunload if
  1156.           // it chooses.
  1157.           window.close();
  1158.         }
  1159.       }
  1160.     }
  1161.   },
  1162.  
  1163.       
  1164.   /**
  1165.    * Indicates to the rest of the system whether or not to 
  1166.    * enable titlebars when opening windows
  1167.    */
  1168.   _setChromeEnabled: function _setChromeEnabled(enabled) {
  1169.  
  1170.     // Set the global chrome (window border and title) flag
  1171.     this._showChromeDataRemote.boolValue = enabled;
  1172.  
  1173.     var prefs = Cc["@mozilla.org/preferences-service;1"]
  1174.                           .getService(Ci.nsIPrefBranch);
  1175.  
  1176.     // Set the flags used to open the core window on startup.
  1177.     // Do a replacement in order to preserve whatever other features 
  1178.     // were specified.
  1179.     try {
  1180.       var titlebarRegEx = /(titlebar=)(no|yes)/;
  1181.       var replacement = (enabled) ? "$1yes" : "$1no";
  1182.       var defaultChromeFeatures = prefs.getCharPref("toolkit.defaultChromeFeatures");
  1183.       prefs.setCharPref("toolkit.defaultChromeFeatures",
  1184.               defaultChromeFeatures.replace(titlebarRegEx, replacement));
  1185.     } catch (e) {
  1186.       Components.utils.reportError("FeathersManager._setChromeEnabled: Error setting " + 
  1187.                                    "defaultChromeFeatures pref! " + e.toString);
  1188.     }
  1189.   },      
  1190.       
  1191.  
  1192.   /**
  1193.    * Broadcasts an update event to all registered listeners
  1194.    */
  1195.   _onUpdate: function onUpdate() {
  1196.     this._listeners.forEach( function (listener) {
  1197.       listener.onFeathersUpdate();
  1198.     });
  1199.   },
  1200.  
  1201.  
  1202.   /**
  1203.    * Broadcasts an select (feathers switch) event to all registered listeners
  1204.    */
  1205.   _onSelect: function onSelect(layoutDesc, skinDesc) {
  1206.     // Verify args
  1207.     layoutDesc = layoutDesc.QueryInterface(Ci.sbILayoutDescription);
  1208.     skinDesc = skinDesc.QueryInterface(Ci.sbISkinDescription);
  1209.     
  1210.     // Broadcast notification
  1211.     this._listeners.forEach( function (listener) {
  1212.       listener.onFeathersSelectRequest(layoutDesc, skinDesc);
  1213.     });
  1214.   },
  1215.  
  1216.   _onSelectComplete: function onSelectComplete() {
  1217.     var layoutDescription = this.getLayoutDescription(this.currentLayoutURL);
  1218.     var skinDescription = this.getSkinDescription(this.currentSkinName);
  1219.  
  1220.     // Broadcast notification
  1221.     this._listeners.forEach( function (listener) {
  1222.       listener.onFeathersSelectComplete(layoutDescription, skinDescription);
  1223.     });
  1224.   },
  1225.  
  1226.   _flushXULPrototypeCache: function flushXULPrototypeCache() {
  1227.     var prefs = Cc["@mozilla.org/preferences-service;1"]
  1228.                           .getService(Ci.nsIPrefBranch);
  1229.     var disabled = false;
  1230.     var userPref = false;
  1231.  
  1232.     try {
  1233.       disabled = prefs.getBoolPref("nglayout.debug.disable_xul_cache");
  1234.       userPref = true;
  1235.     }
  1236.     catch(e) {
  1237.     }
  1238.  
  1239.     if (!disabled) {
  1240.       prefs.setBoolPref("nglayout.debug.disable_xul_cache", true);
  1241.       prefs.setBoolPref("nglayout.debug.disable_xul_cache", false);
  1242.       if (!userPref) {
  1243.         prefs.clearUserPref("nglayout.debug.disable_xul_cache");
  1244.       }
  1245.     }
  1246.   },
  1247.  
  1248.   /**
  1249.    * Called by the observer service. Looks for XRE shutdown messages 
  1250.    */
  1251.   observe: function(subject, topic, data) {
  1252.     var os      = Cc["@mozilla.org/observer-service;1"]
  1253.                       .getService(Ci.nsIObserverService);
  1254.     switch (topic) {
  1255.     case "quit-application":
  1256.       os.removeObserver(this, "quit-application");
  1257.       this._deinit();
  1258.       break;
  1259.     }
  1260.   },
  1261.  
  1262.   /**
  1263.    * Check if an addon is disabled, and if so, re-enables it.
  1264.    * Note that this only checks for the userDisabled flag,
  1265.    * not appDisabled, so addons that have been disabled because
  1266.    * of a compatibility or security issue remain disabled.
  1267.    */
  1268.   _ensureAddOnEnabled: function(id) {
  1269.     const nsIUpdateItem = Ci.nsIUpdateItem;
  1270.     var em = Cc["@mozilla.org/extensions/manager;1"]
  1271.                .getService(Ci.nsIExtensionManager);
  1272.     var ds = em.datasource; 
  1273.     var rdf = Cc["@mozilla.org/rdf/rdf-service;1"]
  1274.                 .getService(Ci.nsIRDFService);
  1275.  
  1276.     var resource = rdf.GetResource("urn:mozilla:item:" + id); 
  1277.  
  1278.     var property = rdf.GetResource("http://www.mozilla.org/2004/em-rdf#userDisabled");
  1279.     var target = ds.GetTarget(resource, property, true);
  1280.  
  1281.     function getData(literalOrResource) {
  1282.       if (literalOrResource instanceof Ci.nsIRDFLiteral ||
  1283.           literalOrResource instanceof Ci.nsIRDFResource ||
  1284.           literalOrResource instanceof Ci.nsIRDFInt)
  1285.         return literalOrResource.Value;
  1286.       return undefined;
  1287.     }
  1288.  
  1289.     var userDisabled = getData(target);
  1290.       
  1291.     if (userDisabled == "true") {
  1292.       em.enableItem(id); 
  1293.     }
  1294.   }, 
  1295.  
  1296.   /**
  1297.    * See nsISupports.idl
  1298.    */
  1299.   QueryInterface: function(iid) {
  1300.     if (!iid.equals(IID) &&
  1301.         !iid.equals(Ci.nsIObserver) && 
  1302.         !iid.equals(Ci.nsISupports))
  1303.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1304.     return this;
  1305.   }
  1306. }; // FeathersManager.prototype
  1307.  
  1308. /**
  1309.  * Callback helper for FeathersManager::switchFeathers
  1310.  * This is needed to make sure the window is really closed before we switch skins
  1311.  */
  1312. function FeathersManager_switchFeathers_callback(aFeathersManager,
  1313.                                                  aLayoutURL,
  1314.                                                  aInternalName) {
  1315.   this.feathersManager = aFeathersManager;
  1316.   this.layoutURL = aLayoutURL;
  1317.   this.internalName = aInternalName;
  1318. }
  1319.  
  1320. FeathersManager_switchFeathers_callback.prototype = {
  1321.   /**
  1322.    * \sa nsITimerCallback
  1323.    */
  1324.   notify: function FeathersManager_switchFeathers_callback_notify() {
  1325.     // Set new values
  1326.     this.feathersManager._layoutDataRemote.stringValue = this.layoutURL;
  1327.     this.feathersManager._skinDataRemote.stringValue = this.internalName;
  1328.  
  1329.     this.feathersManager._flushXULPrototypeCache();
  1330.     this.feathersManager.openPlayerWindow();
  1331.     this.feathersManager._switching = false;
  1332.     this.feathersManager._onSelectComplete();
  1333.     this.feathersManager = null;
  1334.   }
  1335. }; // FeathersManager_switchFeathers_callback.prototype
  1336.  
  1337.  
  1338.  
  1339.  
  1340.  
  1341. /**
  1342.  * ----------------------------------------------------------------------------
  1343.  * Registration for XPCOM
  1344.  * ----------------------------------------------------------------------------
  1345.  */
  1346. var gModule = {
  1347.   registerSelf: function(componentManager, fileSpec, location, type) {
  1348.     componentManager = componentManager.QueryInterface(Ci.nsIComponentRegistrar);
  1349.  
  1350.     componentManager.registerFactoryLocation(CID, CLASSNAME, CONTRACTID,
  1351.                                                fileSpec, location, type);
  1352.   },
  1353.  
  1354.   getClassObject: function(componentManager, cid, iid) {
  1355.     if (!iid.equals(Ci.nsIFactory))
  1356.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  1357.  
  1358.     if (cid.equals(CID)) {
  1359.        return { 
  1360.           createInstance: function(outer, iid) {
  1361.             if (outer != null)
  1362.               throw Components.results.NS_ERROR_NO_AGGREGATION;
  1363.               
  1364.             // Make the feathers manager  
  1365.             return (new FeathersManager()).QueryInterface(iid);;
  1366.           }
  1367.        };
  1368.     }
  1369.     
  1370.     throw Components.results.NS_ERROR_NO_INTERFACE;
  1371.   },
  1372.  
  1373.   canUnload: function(componentManager) { 
  1374.     return true; 
  1375.   }
  1376. }; // gModule
  1377.  
  1378. function NSGetModule(comMgr, fileSpec) {
  1379.   return gModule;
  1380. } // NSGetModule
  1381.  
  1382.  
  1383.  
  1384.